package com.bolt.support.spring.jpa.jpql;

import com.bolt.common.collection.CollectionUtil;
import com.bolt.common.convert.Convert;
import com.bolt.common.reflect.ReflectionUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.convention.data.Record;
import com.bolt.support.query.conditions.domain.SqlClause;

import javax.persistence.EntityManager;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

public class DefaultQueryMetadata implements QueryMetadata {

    private List<SqlClause<?>> groupBy = new ArrayList();

    private List<SqlClause<?>> select = new ArrayList();

    private OperationClause where;

    private OperationClause joinCondition;

    private OperationClause having;

    private List<JoinClause> joins = new ArrayList();

    private EntityClause joinTarget;

    private JoinType joinType;

    private List<SqlClause<?>> orderBy = new ArrayList();

    private Class<?> returnClass;

    private Properties param = new Properties();

    private Integer limit;

    private Integer offset;

    private boolean distinct;

    private boolean allField;

    private List<SqlClause<?>> updates = new ArrayList<>();

    private List<DynamicCondition> dynamicConditions = new ArrayList<>();

    // 查询结果处理器
    private List resultProcessor = new ArrayList();

    private boolean subCount = false;

    private boolean nativeQuery = false;

    private HqlTranslator nativeTranslator;

    public DefaultQueryMetadata(EntityManager em) {
        this.nativeTranslator = new HqlTranslator(em);
    }

    private OperationClause and(OperationClause lhs, OperationClause rhs) {
        if (lhs == null) {
            return rhs;
        } else {
            return Clauses.and(lhs, rhs);
        }
    }

    private void add(List<SqlClause<?>> collection, SqlClause<?>... clauses) {
        for (SqlClause<?> clause : clauses) {
            collection.add(clause);
        }
    }

    @Override
    public void setReturnClass(Class<?> clazz) {
        this.returnClass = clazz;
    }

    @Override
    public void addSelect(SqlClause<?>... column) {
        add(this.select, column);
    }

    @Override
    public void addDynamicConditions(DynamicCondition dynamicCondition) {
        dynamicConditions.add(dynamicCondition);
    }

    @Override
    public void addFrom(EntityClause... args) {
        for (EntityClause arg : args) {
            addJoin(JoinType.DEFAULT, arg);
        }
    }

    @Override
    public void setWhere(OperationClause where) {
        this.where = where;
    }

    @Override
    public void addUpdates(SqlClause<?>... clauses) {
        for (SqlClause<?> clause : clauses) {
            this.updates.add(clause);
        }
    }

    private void addLastJoin() {
        if (joinTarget != null) {
            joins = (List<JoinClause>) CollectionUtil.addAll(joins, new JoinClause(joinType, joinTarget, joinCondition));
            joinType = null;
            joinTarget = null;
            joinCondition = null;
        }
    }


    @Override
    public void addJoin(JoinType joinType, EntityClause entityClause) {
        addLastJoin();
        this.joinType = joinType;
        this.joinTarget = entityClause;
        String alias = entityClause.getAlias();
        if (StrUtil.isNotBlank(alias)) {
            for (SqlClause<?> clause : select) {
                if (clause instanceof ColumnClause) {
                    ColumnClause column = (ColumnClause) clause;
                    if (column.getType().isAssignableFrom(entityClause.getType())) {
                        column.setOwned(alias);
                    }
                }
            }
        }
    }


    @Override
    public void addJoinCondition(OperationClause clause) {
        joinCondition = and(joinCondition, clause);
    }

    @Override
    public List<JoinClause> getJoins() {
        if (joinTarget == null) {
            return joins;
        } else {
            List<JoinClause> j = CollectionUtil.newArrayList(joins);
            j.add(new JoinClause(joinType, joinTarget, joinCondition));
            return j;
        }
    }


    private void extractParam(Collection<? extends SqlClause<?>> clauses) {
        for (SqlClause<?> sqlClause : clauses) {
            if (sqlClause instanceof OperationClause) {
                OperationClause operationClause = (OperationClause) sqlClause;
                extractParam(operationClause.getArgs());
            } else if (sqlClause instanceof ParamClause) {
                ParamClause paramClause = (ParamClause) sqlClause;
                param.put(paramClause.getParamName(), paramClause.getVal());
            } else {
                continue;
            }
        }
    }

    @Override
    public void addGroupBy(SqlClause<?>... column) {
        add(this.groupBy, column);
    }


    @Override
    public void addHaving(OperationClause clause) {
        having = and(having, clause);
    }

    @Override
    public void addOrderBy(SqlClause<?>... clause) {
        add(this.orderBy, clause);
    }


    @Override
    public List<SqlClause<?>> getGroupBy() {
        return groupBy;
    }

    @Override
    public List<SqlClause<?>> getSelect() {
        return select;
    }


    @Override
    public OperationClause getWhere() {
        return where;
    }

    @Override
    public OperationClause getHaving() {
        return having;
    }

    @Override
    public List<SqlClause<?>> getOrderBy() {
        return orderBy;
    }

    @Override
    public Class<?> getReturnClass() {
        return returnClass;
    }

    @Override
    public Properties getParam() {
        if (null != where) {
            extractParam(where.getArgs());
        }
        if (!updates.isEmpty()) {
            extractParam(updates);
        }
        if (null != having) {
            extractParam(having.getArgs());
        }
        return param;
    }

    @Override
    public void setLimit(Long limit) {
        if (null != limit) this.limit = Convert.toInt(limit);
    }

    @Override
    public void setOffset(Long offset) {
        if (null != offset) this.offset = Convert.toInt(offset);
    }

    @Override
    public Integer getLimit() {
        return limit;
    }

    @Override
    public Integer getOffset() {
        return offset;
    }

    @Override
    public boolean isDistinct() {
        return distinct;
    }

    @Override
    public void setDistinct(boolean distinct) {
        this.distinct = distinct;
    }

    @Override
    public List<SqlClause<?>> getUpdates() {
        return updates;
    }

    @Override
    public List<DynamicCondition> getDynamicConditions() {
        return dynamicConditions;
    }

    @Override
    public void addResultProcessor(Object resultPro) {
        this.resultProcessor.add(resultPro);
    }

    @Override
    public List<Object> getResultProcessor() {
        return this.resultProcessor;
    }

    @Override
    public List<String> getAliases() {
        return Clauses.toAliases(getSelect());
    }

    @Override
    public List<Field> getAccessFields() {
        List<Field> accessFields = new ArrayList<>();
        if (this.returnClass != null && !Record.class.isAssignableFrom(this.returnClass)) {
            List<String> aliases = getAliases();
            for (String alias : aliases) {
                accessFields.add(ReflectionUtil.getAccessibleField(returnClass, alias));
            }
        }
        return accessFields;
    }

    @Override
    public boolean isAllField() {
        return allField;
    }

    @Override
    public void setAllField(boolean allField) {
        this.allField = allField;
    }

    @Override
    public boolean isSubCount() {
        return subCount;
    }


    public void setSubCount(boolean subCount) {
        this.nativeQuery = true;
        this.subCount = subCount;
    }

    @Override
    public boolean nativeQuery() {
        return nativeQuery;
    }

    public void setNativeQuery(boolean nativeQuery) {
        this.nativeQuery = nativeQuery;
    }

    @Override
    public HqlTranslator nativeTranslator() {
        return this.nativeTranslator;
    }

    public void setNativeTranslator(HqlTranslator nativeTranslator) {
        this.nativeTranslator = nativeTranslator;
    }
}

 